/* ================================================================
*   Copyright (C) 2021 All rights reserved.
*
*   文件名称：xblock_manager_test.cpp
*   创 建 者：xunmenglong
*   创建日期：2021年01月25日
*   描    述：
*
================================================================ */


#ifndef XBLOCK_MANAGER_TEST_CPP
#define XBLOCK_MANAGER_TEST_CPP

#include <gtest/gtest.h>
#include <gflags/gflags.h>

#include "xblock_manager.h"

inline int exact_size(int unit_num, int unit_size) {
    return unit_num*unit_size-4;
}

TEST(xblock_manager_test, malloc) {
    const char * file = "./test.block";
    if (is_file_exists(file)) {
        remove(file);
    }
    int unit_size = 16;
    int max_num = 20;

    // # case1: 初始化
    xblock_manager manager;
    int ret = manager.init(file, unit_size*max_num, unit_size);
    ASSERT_EQ(ret, 0);

    int capacity_size = 0;

    // # case2: 分配比自己空间更大的
    char * data = manager.malloc(exact_size(max_num, unit_size), &capacity_size);
    ASSERT_TRUE(data == NULL);

    // # case3: 分配19个，也不行，因为19的话就要分配32个
    data = manager.malloc(exact_size(19, unit_size), &capacity_size);
    ASSERT_TRUE(data == NULL);

    // # case4: 分配16个, 就可以分配了
    int cur_size = exact_size(16, unit_size);
    data = manager.malloc(cur_size, &capacity_size);
    ASSERT_TRUE(data != NULL);
    // 容量跟需求一样大
    ASSERT_EQ(capacity_size, cur_size);

    // 释放空间, 准备下次申请
    manager.free(data);

    // # case5: 分配15个，也是分配16个
    cur_size = exact_size(15, unit_size);
    int cur_capacity_size = 0;
    data = manager.malloc(cur_size, &cur_capacity_size);
    ASSERT_TRUE(data != NULL);
    // 15个格子和16个格子的容量其实是一样的
    ASSERT_EQ(capacity_size, cur_capacity_size);
    // 分配到的容量要比剩下的空间要大
    ASSERT_GE(cur_capacity_size, cur_size);

    // # case6: 我还想继续分配8个，预期分配失败
    char * tmp_data = manager.malloc(exact_size(7, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data == NULL);

    // # case7: 分配3个，预期是可以的
    char * last_data = manager.malloc(exact_size(3, unit_size), &capacity_size);
    ASSERT_TRUE(last_data != NULL);

    // # case8: 已经占满了, 再分配1个都不行了, 目前格子是: 16/4
    tmp_data = manager.malloc(exact_size(1, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data == NULL);

    // 将第一个格子删除掉, 现在预期内容为16*/4, *代表是空白
    manager.free(data);

    // # case9: 测试拆分, 预期现在格子的内容为: 1/1*/2*/4*/8*/4
    char * data1 = manager.malloc(exact_size(1, unit_size), &capacity_size);
    ASSERT_TRUE(data1 != NULL);

    // # case10: 测试从1分配到8，预期是都能分配成功
    char * data2 = manager.malloc(exact_size(1, unit_size), &capacity_size);
    char * data3 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    char * data4 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    char * data5 = manager.malloc(exact_size(8, unit_size), &capacity_size);

    ASSERT_TRUE(data2 != NULL);
    ASSERT_TRUE(data3 != NULL);
    ASSERT_TRUE(data4 != NULL);
    ASSERT_TRUE(data5 != NULL);

    // # case11: 测试一下沿同一个拉链分配, 释放两个格子为4的空间
    // 现在格子内容为：1/1/2/4*/8/4*
    manager.free(last_data);
    manager.free(data4);

    char * data4_1 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    char * data4_2 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    ASSERT_TRUE(data4_1 != NULL);
    ASSERT_TRUE(data4_2 != NULL);

    // 结束: 删除文件
    remove(file);
}

TEST(xblock_manager_test, free) {
    const char * file = "./test.block";
    if (is_file_exists(file)) {
        remove(file);
    }
    int unit_size = 16;
    int max_num = 20;
    // 初始化
    xblock_manager manager;
    int ret = manager.init(file, unit_size*max_num, unit_size);
    ASSERT_EQ(ret, 0);

    // 准备将20个格子，分隔为4/2/2/4/8的格子大小
    int capacity_size = 0;
    char * data1 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    char * data2 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    char * data3 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    char * data4 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    char * data5 = manager.malloc(exact_size(8, unit_size), &capacity_size);

    ASSERT_TRUE(data1 != NULL);
    ASSERT_TRUE(data2 != NULL);
    ASSERT_TRUE(data3 != NULL);
    ASSERT_TRUE(data4 != NULL);
    ASSERT_TRUE(data5 != NULL);

    // #case1: 测试释放之后相邻空间会合并
    manager.free(data2);
    manager.free(data3);

    char * tmp_data = manager.malloc(exact_size(4, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data != NULL);

    // 恢复环境, 将环境恢复4/2/2/4/8的格子布局
    manager.free(tmp_data);
    data2 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    data3 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    ASSERT_TRUE(data3-data2 > 0);

    // #case2: 先释放后面，再释放前面，相邻空间依然会合并
    // 调换顺序进行删除
    manager.free(data3);
    manager.free(data2);

    // 继续申请4个格子
    tmp_data = manager.malloc(exact_size(4, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data != NULL);

    // 然后恢复最初环境
    manager.free(tmp_data);
    data2 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    data3 = manager.malloc(exact_size(2, unit_size), &capacity_size);

    // #case3: 释放前3个, 能够申请8个
    manager.free(data1);
    manager.free(data2);
    manager.free(data3);

    tmp_data = manager.malloc(exact_size(8, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data != NULL);

    // 恢复环境
    manager.free(tmp_data);
    data1 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    data2 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    data3 = manager.malloc(exact_size(2, unit_size), &capacity_size);

    // #case4: 前后合并, 释放1、2、4、5、3，然后申请16个，预期成功
    manager.free(data1);
    manager.free(data2);
    manager.free(data5);
    manager.free(data4);
    manager.free(data3);
    tmp_data = manager.malloc(exact_size(16, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data != NULL);

    // #case5: 前后合并
    // @TODO 这个case还没搞定；释放前3个，释放最后一个，再释放中间的4个，能够申请16个
    /*
    manager.free(data1);
    manager.free(data2);
    manager.free(data3);
    manager.free(data5);
    manager.free(data4);
    tmp_data = manager.malloc(exact_size(16, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data != NULL);
    */

    // 结束: 删除文件
    remove(file);
}

TEST(xblock_manager_test, load_data) {
    const char * file = "./test.block";
    if (is_file_exists(file)) {
        remove(file);
    }
    int unit_size = 16;
    int max_num = 20;
    // 初始化
    xblock_manager manager;
    int ret = manager.init(file, unit_size*max_num, unit_size);
    ASSERT_EQ(ret, 0);

    // 首先对数据文件进行分配
    int capacity_size = 0;
    char * data1 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    char * data2 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    char * data3 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    char * data4 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    char * data5 = manager.malloc(exact_size(8, unit_size), &capacity_size);

    ASSERT_TRUE(data1 != NULL);
    ASSERT_TRUE(data2 != NULL);
    ASSERT_TRUE(data3 != NULL);
    ASSERT_TRUE(data4 != NULL);
    ASSERT_TRUE(data5 != NULL);

    manager.free(data4);
    // 现在文件的格局为：4/2/2/4*/8

    // 然后完全生成一个全新的对象来读取
    xblock_manager manager2;
    ret = manager2.init(file, unit_size*max_num, unit_size);
    ASSERT_EQ(ret, 0);

    // 分配一个16个的空间，预期分配失败
    char * tmp_data = manager2.malloc(exact_size(16, unit_size), &capacity_size);
    ASSERT_TRUE(tmp_data == NULL);

    // 连续分配3个格子的空间, 预期前2个成功
    data1 = manager2.malloc(exact_size(2, unit_size), &capacity_size);
    data2 = manager2.malloc(exact_size(2, unit_size), &capacity_size);
    data3 = manager2.malloc(exact_size(2, unit_size), &capacity_size);
    ASSERT_TRUE(data1 != NULL);
    ASSERT_TRUE(data2 != NULL);
    ASSERT_TRUE(data3 == NULL);

    // 删除文件
    remove(file);
}

TEST(xblock_manager_test, try_expand_capacity) {
    const char * file = "./test.block";
    if (is_file_exists(file)) {
        remove(file);
    }
    int unit_size = 16;
    int max_num = 20;
    // 初始化
    xblock_manager manager;
    int ret = manager.init(file, unit_size*max_num, unit_size);
    ASSERT_EQ(ret, 0);

    // 准备将20个格子，分隔为4/2/2/4/8*的格子大小
    int capacity_size = 0;
    char * data1 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    char * data2 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    char * data3 = manager.malloc(exact_size(2, unit_size), &capacity_size);
    char * data4 = manager.malloc(exact_size(4, unit_size), &capacity_size);

    ASSERT_TRUE(data1 != NULL);
    ASSERT_TRUE(data2 != NULL);
    ASSERT_TRUE(data3 != NULL);
    ASSERT_TRUE(data4 != NULL);

    // #case1: 从尾巴上扩展
    // 此时布局：4/2/2/8/4*
    bool tmp_result = manager.try_expand_capacity(data4, 0, &capacity_size);
    ASSERT_TRUE(tmp_result);

    // #case2: 只剩下4个了，申请8个，预期失败; 申请4个会成功
    char * data5 = manager.malloc(exact_size(8, unit_size), &capacity_size);
    ASSERT_TRUE(data5 == NULL);

    data5 = manager.malloc(exact_size(4, unit_size), &capacity_size);
    ASSERT_TRUE(data5 != NULL);

    // #case3: 测试一下将3释放了，再扩展2
    manager.free(data3);
    tmp_result = manager.try_expand_capacity(data2, 0, &capacity_size);
    ASSERT_TRUE(tmp_result);

    // # case4: 再扩展一次2, 因为后面空间没释放，所以预期失败
    tmp_result = manager.try_expand_capacity(data2, 0, &capacity_size);
    ASSERT_FALSE(tmp_result);

    // # case5: 将4释放了，再扩展一次2，因为后面有空间了，所以预期成功
    manager.free(data4);
    tmp_result = manager.try_expand_capacity(data2, 0, &capacity_size);
    ASSERT_TRUE(tmp_result);

    // 删除文件
    remove(file);
}

int main(int argc, char ** argv) {
    testing::InitGoogleTest(&argc, argv);
    GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true);
    return RUN_ALL_TESTS();
}

#endif
